package com.yeziji.utils.http;

import com.alibaba.fastjson.JSONObject;
import com.yeziji.utils.DataUtils;
import com.yeziji.utils.HttpUtils;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.protocol.HTTP;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.jsoup.Jsoup;
import org.springframework.util.CollectionUtils;

import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 请求工具类
 *
 * <ul>
 *   <li>配置响应时采用 {@link YzjSendConfig} 对所需参数进行配置，其对应的返回值会添加至 {@link YzjHttpResult} 当中
 *   <li>当 {@link #redirect} 为 {@code Boolean.TRUE} 时，会发送二次请求将 result 是重定向请求结果。但重定向地址会保存至 {@link
 *       YzjHttpResult#getRedirectUrl()} 当中
 *   <li>默认采取 httpclient 线程池维持请求的可持续使用
 *   <li>对于请求方式需要从 {@link HttpMethod} 中获取。TODO: 其中 PUT\HEAD\DELETE 还未进行调试
 * </ul>
 *
 * @author gzkemays
 * @since 2022/1/14 20:19
 */
@Data
public class YzjHttp {
    String url;
    String method;
    boolean redirect;
    boolean cookie;
    int socketTimeout;
    int connectTimeout;
    int connectRequestTimeout;
    Map<String, String> headers;
    YzjHttpHeaders yzjHeaders;
    Charset charset;
    Map<String, Object> dataMap;
    Object dataObj;

    public static RequestConfig getRequestDefaultConfig() {
        return RequestConfig.custom()
                .setSocketTimeout(20000)
                .setConnectTimeout(20000)
                .setConnectionRequestTimeout(20000)
                .build();
    }

    public static YzjHttpBuilder create() {
        return new YzjHttpBuilder();
    }

    public static YzjHttpBuilder create(String url, String method) {
        return new YzjHttpBuilder(url, method);
    }

    public static YzjHttpBuilder createGet(String url) {
        return new YzjHttpBuilder(url, HttpMethod.GET);
    }

    public static YzjHttpBuilder createPost(String url) {
        return new YzjHttpBuilder(url, HttpMethod.POST);
    }

    /**
     * 基本请求配置
     */
    public static final class YzjHttpBuilder {
        String url;
        String method;
        String cookies;
        boolean redirect;
        boolean cookie;
        boolean document;
        boolean json;

        boolean postJson;
        boolean postForm;
        int socketTimeout = 20000;
        int connectTimeout = 20000;
        int connectRequestTimeout = 20000;
        Map<String, Object> dataMap = new HashMap<>();
        Object dataObj;
        Charset charset = StandardCharsets.UTF_8;
        YzjHttpHeaders yzjHeaders;
        Map<String, String> headers = new HashMap<>();
        CookieStore cookieStore = new BasicCookieStore();
        CloseableHttpClient httpClient =
                HttpClients.custom()
                        .setConnectionManager(HttpUtils.poolingHttpClient())
                        .setDefaultCookieStore(cookieStore)
                        .build();

        public YzjHttpBuilder() {
        }

        public YzjHttpBuilder(String url, String method) {
            this.url = url;
            this.method = method;
        }

        public YzjHttpBuilder url(String url) {
            this.url = url;
            return this;
        }

        public YzjHttpBuilder method(String method) {
            if (!HttpMethod.METHODS.contains(method.toUpperCase())) {
                throw new IllegalArgumentException("不存在对应请求，请查阅 cn.yzj.common.HttpMethod 中的参数。");
            }
            this.method = method;
            return this;
        }

        public YzjHttpBuilder config(YzjSendConfig config) {
            this.redirect = config.redirect;
            this.document = config.document;
            this.json = config.json;
            this.cookie = config.cookie;
            this.postForm = config.postForm;
            this.postJson = config.postJson;
            return this;
        }

        public YzjHttpBuilder cookies(String cookies) {
            this.cookies = cookies;
            return this;
        }

        public YzjHttpBuilder headers(Map<String, String> headers) {
            this.headers = headers;
            return this;
        }

        public YzjHttpBuilder socketTimeout(int socketTimeout) {
            this.socketTimeout = socketTimeout;
            return this;
        }

        public YzjHttpBuilder connectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
            return this;
        }

        public YzjHttpBuilder connectRequestTimeout(int connectRequestTimeout) {
            this.connectRequestTimeout = connectRequestTimeout;
            return this;
        }

        public YzjHttpBuilder dataMap(Map<String, Object> dataMap) {
            this.dataMap = dataMap;
            return this;
        }

        public YzjHttpBuilder dataObj(Object dataObj) {
            this.dataObj = dataObj;
            return this;
        }

        public YzjHttpBuilder charset(Charset charset) {
            this.charset = charset;
            return this;
        }

        public YzjHttpBuilder defaultUserAgent() {
            this.headers.put(
                    HTTP.USER_AGENT,
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36 Edg/97.0.1072.62");
            return this;
        }

        public YzjHttpBuilder yzjHeaders(YzjHttpHeaders yzjHeaders) {
            this.yzjHeaders = yzjHeaders;
            return this;
        }

        public YzjHttpResult send() {
            YzjHttpResult yhr = new YzjHttpResult();
            YzjHttp yzjHttp = new YzjHttp();
            if (url == null || method == null) {
                throw new NullPointerException("请确保 url 和 method 不为 null");
            }
            yzjHttp.setUrl(url);
            yzjHttp.setMethod(method);
            yzjHttp.setRedirect(redirect);
            yzjHttp.setCookie(cookie);
            yzjHttp.setSocketTimeout(socketTimeout);
            yzjHttp.setConnectTimeout(connectTimeout);
            yzjHttp.setConnectRequestTimeout(connectRequestTimeout);
            yzjHttp.setDataMap(dataMap);
            yzjHttp.setDataObj(dataObj);

            String result = execute();
            assert result != null;
            // FIXME: 连续请求，可能需要换个方式截取 redirect。
            if (redirect) {
                yhr.setRedirectUrl(result);
                url = result;
                redirect = false;
                yhr.setResult(execute());
                redirect = true;
            } else {
                if (document) {
                    yhr.setDocument(Jsoup.parse(result));
                }
                if (json) {
                    yhr.setJson(JSONObject.parseObject(result));
                }
                yhr.setResult(result);
            }
            if (cookie) {
                yhr.setCookie(HttpUtils.getCookie(cookieStore));
            }
            return yhr;
        }

        /**
         * 获取请求配置
         */
        private RequestConfig getRequestConfig() {
            return RequestConfig.custom()
                    .setSocketTimeout(socketTimeout)
                    .setConnectionRequestTimeout(connectRequestTimeout)
                    .setConnectTimeout(connectTimeout)
                    .setRedirectsEnabled(!redirect)
                    .build();
        }

        /**
         * 根据配置构造对应的 http post 请求，默认返回 {@link HttpPost}
         */
        private HttpPost getPost() {
            if (postJson) {
                if (dataObj instanceof String) {
                    return HttpUtils.getPostJson(url, String.valueOf(dataObj));
                } else if (dataObj != null) {
                    return HttpUtils.getPostJson(url, JSONObject.toJSONString(dataObj));
                }
                if (!dataMap.isEmpty()) {
                    return HttpUtils.getPostJson(url, JSONObject.toJSONString(dataMap));
                }
                throw new NullPointerException("传参为 null，请确保配置了 dataMap 或 dataObj ");
            }
            if (postForm) {
                if (!dataMap.isEmpty()) {
                    return HttpUtils.getUrlEncodedFormPost(url, dataMap, charset);
                } else if (dataObj != null) {
                    return HttpUtils.getUrlEncodedFormPost(url, DataUtils.getMapFromObject(dataObj), charset);
                }
                throw new NullPointerException("传参为 null，请确保配置了 dataMap 或 dataObj ");
            }
            return HttpUtils.getHttpPost(url);
        }

        /**
         * 执行发送请求
         *
         * @return 请求结果
         */
        // TODO：未编写 PUT\DELETE\HEAD 的请求
        private String execute() {
            CloseableHttpResponse response;
            try {
                switch (method) {
                    case HttpMethod.GET:
                        // 发送 GET 请求
                        HttpGet get =
                                dataMap.isEmpty() ? HttpUtils.getHttpGet(url) : HttpUtils.getDataGet(url, dataMap);
                        get.setConfig(getRequestConfig());
                        setHttpGetHeaders(get, headers);
                        response = httpClient.execute(get);
                        if (redirect) {
                            return response.getFirstHeader("location").getValue();
                        }
                        return EntityUtils.toString(response.getEntity(), charset);
                    case HttpMethod.POST:
                        // 发送 POST 请求
                        HttpPost post = getPost();
                        post.setConfig(getRequestConfig());
                        setHttpPostHeaders(post, headers);
                        response = httpClient.execute(post);
                        if (redirect) {
                            return response.getFirstHeader("location").getValue();
                        }
                        return EntityUtils.toString(response.getEntity(), charset);
                    case HttpMethod.PUT:
                        // 发送 PUT 请求
                        break;
                    case HttpMethod.DELETE:
                        // 发送 DELETE 请求
                        break;
                    case HttpMethod.HEAD:
                        // 发送 HEAD 请求
                        break;
                    default:
                        throw new IllegalArgumentException("无指定参数");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 忽略 SSL 证书
         */
        private SSLContext getIgnoreSslContext() {
            try {
                return SSLContexts.custom()
                        .loadTrustMaterial(
                                null,
                                new TrustStrategy() {
                                    @Override
                                    public boolean isTrusted(X509Certificate[] x509Certificates, String s) {
                                        return true;
                                    }
                                })
                        .build();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }

        private void setHttpGetHeaders(HttpGet get, Map<String, String> headers) {
            if (StringUtils.isNotBlank(cookies)) {
                get.setHeader("Cookie", cookies);
            }
            if (yzjHeaders != null) {
                String key = yzjHeaders.getKey();
                String value = yzjHeaders.getValue();
                if (Objects.nonNull(key) && Objects.nonNull(value)) {
                    get.setHeader(key, value);
                }
                List<String> keys = yzjHeaders.getKeys();
                List<String> values = yzjHeaders.getValues();
                if (!CollectionUtils.isEmpty(keys) && !CollectionUtils.isEmpty(values)) {
                    for (int i = 0; i < keys.size(); i++) {
                        get.setHeader(keys.get(i), values.get(i));
                    }
                }
            }

            if (!headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    // 循环加入
                    get.setHeader(entry.getKey(), entry.getValue());
                }
            }
        }

        private void setHttpPostHeaders(HttpPost post, Map<String, String> headers) {
            if (StringUtils.isNotBlank(cookies)) {
                post.setHeader("Cookie", cookies);
            }
            if (yzjHeaders != null) {
                String key = yzjHeaders.getKey();
                String value = yzjHeaders.getValue();
                if (Objects.nonNull(key) && Objects.nonNull(value)) {
                    post.setHeader(key, value);
                }
                List<String> keys = yzjHeaders.getKeys();
                List<String> values = yzjHeaders.getValues();
                if (!CollectionUtils.isEmpty(keys) && !CollectionUtils.isEmpty(values)) {
                    for (int i = 0; i < keys.size(); i++) {
                        post.setHeader(keys.get(i), values.get(i));
                    }
                }
            }
            if (!headers.isEmpty()) {
                for (Map.Entry<String, String> entry : headers.entrySet()) {
                    // 循环加入
                    post.setHeader(entry.getKey(), entry.getValue());
                }
            }
        }
    }
}
